﻿#include "lua_debug.hh"
#include "../../util/os_util.hh"
#include "../../util/string_util.hh"
#include "../bitwise_enum_class.hh"
#include "../websocket/websocket_impl.hh"
#include "lua_util.hh"

#include <unordered_set>

using namespace kratos::service;

template <>
struct support_bitwise_enum<kratos::lua::DebugMode> : std::true_type {};

/**
 * @brief 不需要传送到调试器对端的变量名、函数名、包名
 */
static std::unordered_set<std::string> LUA_LIBS = {
    "_G",          "_ENV",     "table",        "io",
    "os",          "string",   "coroutine",    "utf8",
    "bit32",       "math",     "debug",        "package",
    "ctx",         "ipairs",   "next",         "rawlen",
    "hotfix_file", "rawset",   "setmetatable", "_VERSION",
    "require",     "rawequal", "print_r",      "requir",
    "pcall",       "tonumber", "hotfix",       "getmetatable",
    "pairs",       "xpcall",   "rawget",       "error",
    "load",        "loadfile", "type",         "collectgarbage",
    "select",      "assert",   "print",        "tostring",
    "dofile"};
/**
 * @brief 检测是否需要过滤
 * @param lib_name 函数或包名
 * @return true或false
 */
bool is_lua_lib(const std::string &lib_name) {
  if (kratos::util::startWith(lib_name, "kratos_") ||
      kratos::util::startWith(lib_name, "_")) {
    return true;
  }
  return (LUA_LIBS.find(lib_name) != LUA_LIBS.end());
}

kratos::lua::Debugger::Debugger(lua_State *L, const std::string &name) {
  L_ = L;
  name_ = name;
  // 自动启动debug hook
  install_hook();
}

kratos::lua::Debugger::~Debugger() {
  disable();
  // 取消debug hook
  uninstall_hook();
}

auto kratos::lua::Debugger::update(std::time_t ms) -> void {}

auto kratos::lua::Debugger::set_source_root(const std::string &source_root)
    -> void {
  source_root_ = source_root;
}

auto kratos::lua::Debugger::get_source_root() -> const std::string & {
  return source_root_;
}

auto kratos::lua::Debugger::set_name(const std::string &name) -> void {
  name_ = name;
}

auto kratos::lua::Debugger::get_name() const -> const std::string & {
  return name_;
}

auto kratos::lua::Debugger::disable() -> void {
  if (!enable_) {
    return;
  }
  enable_ = false;
  current_func_ = "";
  debug_mode_ = DebugMode::RUN;
  step_out_func_ = "";
  step_over_func_ = "";
  step_in_hit_ = false;
  step_out_hit_ = false;
  step_over_hit_ = false;
  suspend_ = false;
  cur_break_id_ = "";
  current_frame_ = 0;
  stack_vector_.clear();
  while (!func_name_stack_.empty()) {
    func_name_stack_.pop();
  }
  for (const auto &[k, v] : breakpoint_map_) {
    disable_breakpoint(k);
  }
  if (debug_thread_ && (LUA_YIELD == lua_status(debug_thread_))) {
    lua_resume(debug_thread_, nullptr, 0);
  }
  debug_thread_ = nullptr;
}

auto kratos::lua::Debugger::enable() -> void {
  for (const auto &[k, v] : breakpoint_map_) {
    if (v.state == BreakPointState::ENABLE) {
      // 重新设置所有开启的断点
      enable_breakpoint(k);
    }
  }
  enable_ = true;
}

auto kratos::lua::Debugger::is_enable() -> bool { return enable_; }

auto kratos::lua::Debugger::add_breakpoint(
    const kratos::service::BreakpointMap &breakpoints, bool all_on) -> bool {
  for (const auto &[k, v] : breakpoints) {
    auto break_id = add_breakpoint(v.file, v.line);
    if (!all_on) {
      if (v.state == BreakPointState::DISABLE) {
        disable_breakpoint(k);
      }
    }
  }
  return true;
}

auto kratos::lua::Debugger::add_breakpoint(const std::string &file, int line)
    -> BreakID {
  // 生成断点ID
  auto break_id = file + "_" + std::to_string(line);
  auto it = breakpoint_map_.find(break_id);
  if (it != breakpoint_map_.end()) {
    return break_id;
  }
  // 添加到断点表
  breakpoint_map_.emplace(break_id,
                          BreakPoint{file, line, BreakPointState::ENABLE});
  return break_id;
}

auto kratos::lua::Debugger::remove_breakpoint(BreakID break_id) -> void {
  breakpoint_map_.erase(break_id);
}

auto kratos::lua::Debugger::enable_breakpoint(BreakID break_id) -> void {
  auto it = breakpoint_map_.find(break_id);
  if (it == breakpoint_map_.end()) {
    return;
  }
  it->second.state = BreakPointState::ENABLE;
}

auto kratos::lua::Debugger::disable_breakpoint(BreakID break_id) -> void {
  auto it = breakpoint_map_.find(break_id);
  if (it == breakpoint_map_.end()) {
    return;
  }
  it->second.state = BreakPointState::DISABLE;
}

auto kratos::lua::Debugger::execute(std::string &error) -> bool {
  if (!check_execution_condition()) {
    error = "No thread in debug mode";
    return false;
  }
  error = "";
  debug_mode_ |= DebugMode::RUN;
  lua_sethook(L_, &Debugger::debug_hook, LUA_MASKLINE, 1);
  suspend_ = false;
  return resume(error);
}

auto kratos::lua::Debugger::step_in(std::string &error) -> bool {
  if (!check_execution_condition()) {
    error = "No thread in debug mode";
    return false;
  }
  error = "";
  suspend_ = false;
  debug_mode_ |= DebugMode::STEP_IN;
  return resume(error);
}

auto kratos::lua::Debugger::step_out(std::string &error) -> bool {
  if (!check_execution_condition()) {
    error = "No thread in debug mode";
    return false;
  }
  step_out_func_ = current_func_;
  error = "";
  suspend_ = false;
  debug_mode_ |= DebugMode::STEP_OUT;
  return resume(error);
}

auto kratos::lua::Debugger::step_over(std::string &error) -> bool {
  if (!check_execution_condition()) {
    error = "No thread in debug mode";
    return false;
  }
  step_over_func_ = current_func_;
  error = "";
  suspend_ = false;
  debug_mode_ |= DebugMode::STEP_OVER;
  return resume(error);
}

auto kratos::lua::Debugger::get_stack() const -> const StackBase & {
  if (current_frame_ >= (int)stack_vector_.size()) {
    return current_stack_;
  }
  return stack_vector_[current_frame_];
}

auto kratos::lua::Debugger::print(const std::string &name, std::string &value)
    -> bool {
  if (!debug_thread_) {
    value = "No thread in debug mode";
    return false;
  }
  const auto &stack = stack_vector_[current_frame_];
  for (const auto &v : stack.local_variables) {
    if (v.name == name) {
      value = v.to_value_string();
      return true;
    }
  }
  for (const auto &v : stack.stack_upvalues) {
    if (v.name == name) {
      value = v.to_value_string();
      return true;
    }
  }
  value = "Variable not found";
  return true;
}

auto kratos::lua::Debugger::lua_table_to_debug_string(
    lua_State *l, std::string &str, const std::string &last_tab,
    const std::string &tab) -> bool {
  if (!lua_istable(l, -1)) {
    return false;
  }
  str += last_tab + "{\n";
  // 遍历Lua表
  lua_pushnil(l);
  while (lua_next(l, -2)) {
    std::string key_str;
    std::string value_str;
    Variable::to_value_string(l, -2, key_str);
    if (!is_lua_lib(key_str)) {
      if (lua_istable(l, -1)) {
        str += tab + "  " + key_str + " =>\n";
        lua_table_to_debug_string(l, value_str, tab, tab + "  ");
        str += tab + "  " + value_str + "\n";
      } else {
        str += tab + "  " + key_str + " => ";
        Variable::to_value_string(l, -1, value_str);
        str += value_str + "\n";
      }
    }
    lua_pop(l, 1);
  }
  str += tab + "}\n";
  return true;
}

auto kratos::lua::Debugger::check_execution_condition() -> bool {
  if (!debug_thread_ || (lua_status(debug_thread_) != LUA_YIELD) || !suspend_) {
    return false;
  }
  return true;
}

auto kratos::lua::Debugger::eval(const std::string &code, std::string &result)
    -> bool {
  if (LUA_OK != lua_status(L_)) {
    result = "machine status is not ready";
    return false;
  }
  LuaUtil::StackGuard guard(L_);
  auto top = lua_gettop(L_);
  auto error = luaL_dostring(L_, code.c_str());
  if (!LuaUtil::catch_lua_error(L_, error, result)) {
    return false;
  }
  auto ret_num = lua_gettop(L_) - top;
  if (!ret_num) {
    result = "Success";
    return true;
  }
  result += "Return:\n";
  for (int i = ret_num; i >= 1; i--) {
    auto index = -1 * i;
    std::string str_value;
    if (lua_type(L_, index) == LUA_TTABLE) {
      lua_pushvalue(L_, -1);
      lua_table_to_debug_string(L_, str_value, "", "");
      lua_pop(L_, 1);
    } else {
      Variable::to_value_string(L_, index, str_value);
    }
    result += std::to_string(ret_num - i + 1) + ": " + str_value + "\n";
  }
  return true;
}

auto kratos::lua::Debugger::get_unique_id() const -> std::uint64_t {
  if (!debug_thread_) {
    return std::uint64_t(0);
  }
  if (!suspend_) {
    return std::uint64_t(0);
  } else {
    return (std::uint64_t)debug_thread_;
  }
}

auto kratos::lua::Debugger::get_backtrace(int level,
                                          std::string &backtrace) const
    -> bool {
  std::stringstream ss;
  if (level == -1) {
    // 获取全部深度的堆栈
    int i = 0;
    for (const auto &stack : stack_vector_) {
      ss << i++ << "  " << stack.file_name << "(" << stack.func_name << ":"
         << stack.line << ")(" << stack.func_type << ":" << stack.func_domain
         << ")" << std::endl;
    }
  } else {
    // 获取level深度的堆栈
    if (level >= (int)stack_vector_.size() || level < -1) {
      return false;
    }
    const auto &stack = stack_vector_[level];
    ss << level << "  " << stack.file_name << "(" << stack.func_name << ":"
       << stack.line << ")(" << stack.func_type << ":" << stack.func_domain
       << ")";
  }
  backtrace = ss.str();
  return true;
}

auto kratos::lua::Debugger::frame(int level) -> bool {
  if (level >= (int)stack_vector_.size()) {
    return false;
  }
  current_frame_ = level;
  return true;
}

auto kratos::lua::Debugger::get_machine_version() -> std::string {
  return LUA_RELEASE;
}

auto kratos::lua::Debugger::hook(lua_State *thread) -> void {
  lua_sethook(thread, &Debugger::debug_hook,
              LUA_MASKLINE | LUA_MASKCALL | LUA_MASKRET, 1);
}

auto kratos::lua::Debugger::unhook(lua_State *thread) -> void {
  std::string error;
  if (!LuaUtil::resume(thread, error, 0)) {
    // TODO error
  }
  if (debug_thread_ == thread) {
    // 调试结束
    debug_thread_ = nullptr;
  }
  lua_sethook(thread, &Debugger::debug_hook, 0, 0);
}

auto kratos::lua::Debugger::get_all_breakpoint() const
    -> const BreakpointMap & {
  return breakpoint_map_;
}

auto kratos::lua::Debugger::set_cb(DebuggerCallback cb) -> void { cb_ = cb; }

auto kratos::lua::Debugger::get_cb() -> DebuggerCallback { return cb_; }

auto kratos::lua::Debugger::is_suspend() -> bool { return suspend_; }

auto kratos::lua::Debugger::install_hook() -> bool {
  LuaUtil::lua_push(L_, (void *)this);
  lua_setfield(L_, LUA_REGISTRYINDEX, "debugger");
  lua_sethook(L_, &Debugger::debug_hook,
              LUA_MASKLINE | LUA_MASKCALL | LUA_MASKRET, 1);
  return true;
}

auto kratos::lua::Debugger::uninstall_hook() -> bool {
  lua_sethook(L_, &Debugger::debug_hook, 0, 0);
  return true;
}

auto kratos::lua::Debugger::get_value(lua_State *L, std::any &value, int &type)
    -> bool {
  type = lua_type(L, -1);
  switch (type) {
  case LUA_TNIL:
    value = nullptr;
    break;
  case LUA_TBOOLEAN:
    value = (bool)lua_toboolean(L, -1);
    break;
  case LUA_TNUMBER:
    if (lua_isinteger(L, -1)) {
      type = LUA_TINTEGER;
      value = (std::int64_t)lua_tointeger(L, -1);
    } else {
      value = (double)lua_tonumber(L, -1);
    }
    break;
  case LUA_TSTRING:
    value = (const char *)lua_tostring(L, -1);
    break;
  case LUA_TTABLE: {
    std::string str;
    lua_table_to_debug_string(L, str, "", "");
    value = str;
    break;
  }
  case LUA_TFUNCTION:
  case LUA_TLIGHTUSERDATA:
  case LUA_TUSERDATA:
  case LUA_TTHREAD:
    value = (std::ptrdiff_t)lua_topointer(L, -1);
    break;
  default:
    return false;
  }
  return true;
}

auto kratos::lua::Debugger::get_short_value(lua_State *L, std::any &value,
                                            int &type) -> bool {
  type = lua_type(L, -1);
  switch (type) {
  case LUA_TNIL:
    value = nullptr;
    break;
  case LUA_TBOOLEAN:
    value = (bool)lua_toboolean(L, -1);
    break;
  case LUA_TNUMBER:
    if (lua_isinteger(L, -1)) {
      type = LUA_TINTEGER;
      value = (std::int64_t)lua_tointeger(L, -1);
    } else {
      value = (double)lua_tonumber(L, -1);
    }
    break;
  case LUA_TSTRING:
    value = (const char *)lua_tostring(L, -1);
    break;
  case LUA_TTABLE:
  case LUA_TFUNCTION:
  case LUA_TLIGHTUSERDATA:
  case LUA_TUSERDATA:
  case LUA_TTHREAD:
    value = (std::ptrdiff_t)lua_topointer(L, -1);
    break;
  default:
    return false;
  }
  return true;
}

kratos::lua::Debugger *kratos::lua::Debugger::get_lua_debug(lua_State *L) {
  LuaUtil::get_registry_value(L, "debugger");
  return LuaUtil::pop_ptr_value<Debugger *>(L);
}

auto kratos::lua::Debugger::debug_hook(lua_State *L, lua_Debug *ar) -> void {
  auto *debugger = get_lua_debug(L);
  if (ar->event == LUA_HOOKLINE) {
    debugger->line_hook(L, ar);
  } else if (ar->event == LUA_HOOKCALL) {
    debugger->call_hook(L, ar);
  } else if (ar->event == LUA_HOOKRET) {
    debugger->ret_hook(L, ar);
  }
}

auto kratos::lua::Debugger::build_all_stack() -> void {
  stack_vector_.clear();
  if (!get_stack(current_stack_, 0)) {
    return;
  }
  stack_vector_.emplace_back(current_stack_);
  int level = 1;
  Stack stack;
  while (get_stack(stack, level++)) {
    stack_vector_.emplace_back(stack);
  }
}

auto kratos::lua::Debugger::get_stack(Stack &stack, int level) -> bool {
  stack.clear();
  lua_Debug ar;
  if (!lua_getstack(debug_thread_, level, &ar)) {
    return false;
  }
  lua_getinfo(debug_thread_, "Slnu", &ar);
  // 记录调试器栈信息
  stack.line = ar.currentline;
  std::string source;
  if (ar.source) {
    source = util::ltrim(ar.source, "@");
  } else {
    source = "";
  }
  stack.file_name = source;
  stack.func_name = ar.name ? ar.name : "";
  stack.func_type = ar.namewhat ? ar.namewhat : "";
  stack.func_domain = ar.what ? ar.what : "";
  if (ar.source) {
    util::get_file_content(source, ar.currentline, 5, stack.bp_content);
  }
  const char *var_name = nullptr;
  const static std::string UNKNOWN_LOCAL_NAME = "(*temporary)";
  stack.local_variables.clear();
  for (int i = 1; (var_name = lua_getlocal(debug_thread_, &ar, i++));) {
    Variable var;
    if (var_name && var_name != UNKNOWN_LOCAL_NAME) {
      var.name = var_name;
      get_short_value(debug_thread_, var.short_value, var.lua_type_id);
      get_value(debug_thread_, var.value, var.lua_type_id);
      stack.local_variables.emplace_back(var);
    }
    lua_pop(debug_thread_, 1);
  }
  stack.stack_upvalues.clear();
  lua_getinfo(debug_thread_, "f", &ar);
  for (int i = 1; (var_name = lua_getupvalue(debug_thread_, -1, i++));) {
    Variable var;
    var.name = var_name;
    get_short_value(debug_thread_, var.short_value, var.lua_type_id);
    get_value(debug_thread_, var.value, var.lua_type_id);
    stack.stack_upvalues.emplace_back(var);
    lua_pop(debug_thread_, 1);
  }
  return true;
}

auto kratos::lua::Debugger::line_hook(lua_State *L, lua_Debug *ar) -> void {
  if (!enable_) {
    return;
  }
  bool hit_break = false;
  if (step_in_hit_) {
    // 触发了STEP_IN, 强制断点在第一条执行语句
    hit_break = true;
    step_in_hit_ = false;
  } else if (step_out_hit_) {
    // 触发了STEP_OUT, 强制断点在第一条执行语句
    hit_break = true;
    step_out_hit_ = false;
  } else if (step_over_hit_) {
    // 触发了STEP_OVER, 强制断点在第一条执行语句
    hit_break = true;
    step_over_hit_ = false;
  }
  if (!hit_break) {
    if (!breakpoint_map_.empty()) {
      lua_getinfo(L, "Slnu", ar);
      // 没有命中，检测是否命中断点
      auto key = util::get_file_full_name(ar->short_src) + "_" +
                 std::to_string(ar->currentline);
      auto it = breakpoint_map_.find(key);
      if (it != breakpoint_map_.end()) {
        if (it->second.state == BreakPointState::ENABLE) {
          // 命中断点
          hit_break = true;
          cur_break_id_ = key;
        } else {
          // 断点被禁止
          return;
        }
      }
    }
    if (!hit_break) {
      // 没有命中断点
      return;
    }
  } else {
    // 当前调试协程已经退出
    if (debug_thread_ != L) {
      return;
    }
  }
  // 设置正在调试的协程
  debug_thread_ = L;
  // 获取堆栈信息
  build_all_stack();
  // 挂起虚拟机
  suspend_ = true;
  if (cb_) {
    // 调用回调
    cb_(*this);
  }
  // 断点生效
  lua_yield(L, 0);
}

auto kratos::lua::Debugger::call_hook(lua_State *L, lua_Debug *ar) -> void {
  if (!enable_) {
    return;
  }
  lua_getinfo(L, "n", ar);
  if (ar->name) {
    current_func_ = ar->name;
  } else {
    if (func_name_stack_.empty()) {
      current_func_ = "main";
    } else {
      current_func_ = "";
    }
  }
  func_name_stack_.push(current_func_);
  if ((debug_mode_ & DebugMode::STEP_IN) == DebugMode::STEP_IN) {
    // 进入调用过程完成
    debug_mode_ &= ~DebugMode::STEP_IN;
    step_in_hit_ = true;
  }
}

auto kratos::lua::Debugger::ret_hook(lua_State *L, lua_Debug *ar) -> void {
  if (!enable_) {
    return;
  }
  bool check_step_over = false;
  if ((debug_mode_ & DebugMode::STEP_OUT) == DebugMode::STEP_OUT) {
    if (step_out_func_ == current_func_) {
      // 离开调用过程完成
      debug_mode_ &= ~DebugMode::STEP_OUT;
      step_out_hit_ = true;
      step_out_func_ = "";
    }
  } else if ((debug_mode_ & DebugMode::STEP_OVER) == DebugMode::STEP_OVER) {
    // 需要检查step over
    check_step_over = true;
  }
  if (func_name_stack_.empty()) {
    current_func_ = "";
    return;
  }
  func_name_stack_.pop();
  if (func_name_stack_.empty()) {
    current_func_ = "";
    return;
  }
  if (check_step_over && (func_name_stack_.top() == step_over_func_)) {
    // step over结束
    debug_mode_ &= ~DebugMode::STEP_OVER;
    step_over_hit_ = true;
    step_over_func_ = "";
  }
  // 重新设置当前的函数名
  current_func_ = func_name_stack_.top();
}

auto kratos::lua::Debugger::resume(std::string &error) -> bool {
  if (!LuaUtil::resume(debug_thread_, error, 0)) {
    return false;
  }
  if (lua_status(debug_thread_) != LUA_YIELD) {
    debug_thread_ = nullptr;
  }
  return true;
}
